#!/usr/sw-cluster/apps/Anaconda/anaconda3/bin/python
import struct
import collections
import sys
def list_copy(l):
    return [l[i] for i in range(len(l))]
if sys.version_info.major == 2:
    def iter_unpack(fmt, buf):
        offset = 0
        while offset < len(buf):
            yield struct.unpack_from(fmt, buf, offset)
            offset += struct.calcsize(fmt)
    # list.copy = list_copy
    struct.iter_unpack = iter_unpack
def read_trace(path):
    raw = struct.iter_unpack('Q', open(path, "rb").read())
    traces = [[]]
    for i in raw:
        if i[0] == 0:
            traces.append([])
        else:
            traces[-1].append(i[0])
    return traces
#ehdr_struct = 10sHHIQQQIHHHHHH
import struct
import collections
import sys
# typedef struct
# {
#   unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
#   Elf64_Half    e_type;                 /* Object file type */
#   Elf64_Half    e_machine;              /* Architecture */
#   Elf64_Word    e_version;              /* Object file version */
#   Elf64_Addr    e_entry;                /* Entry point virtual address */
#   Elf64_Off     e_phoff;                /* Program header table file offset */
#   Elf64_Off     e_shoff;                /* Section header table file offset */
#   Elf64_Word    e_flags;                /* Processor-specific flags */
#   Elf64_Half    e_ehsize;               /* ELF header size in bytes */
#   Elf64_Half    e_phentsize;            /* Program header table entry size */
#   Elf64_Half    e_phnum;                /* Program header table entry count */
#   Elf64_Half    e_shentsize;            /* Section header table entry size */
#   Elf64_Half    e_shnum;                /* Section header table entry count */
#   Elf64_Half    e_shstrndx;             /* Section header string table index */
# } Elf64_Ehdr;

ELF64_HDR_FMT = "10sHHIQQQIHHHHHH"
ELF64_Hdr = collections.namedtuple('ELF64_Hdr', ['e_ident', 'e_type', 'e_machine', 'e_version',
                                                 'e_entry', 'e_phoff', 'e_shoff', 'e_flags',
                                                 'e_ehsize', 'e_phentsize', 'e_phnum', 'e_shentsize',
                                                  'e_shnum', 'e_shstrndx'])
# typedef struct
# {
#   Elf64_Word    sh_name;                /* Section name (string tbl index) */
#   Elf64_Word    sh_type;                /* Section type */
#   Elf64_Xword   sh_flags;               /* Section flags */
#   Elf64_Addr    sh_addr;                /* Section virtual addr at execution */
#   Elf64_Off     sh_offset;              /* Section file offset */
#   Elf64_Xword   sh_size;                /* Section size in bytes */
#   Elf64_Word    sh_link;                /* Link to another section */
#   Elf64_Word    sh_info;                /* Additional section information */
#   Elf64_Xword   sh_addralign;           /* Section alignment */
#   Elf64_Xword   sh_entsize;             /* Entry size if section holds table */
# } Elf64_Shdr;

ELF64_SHDR_FMT = "IIQQQQIIQQ"
ELF64_Shdr = collections.namedtuple('ELF64_Shdr', ['sh_name', 'sh_type', 'sh_flags', 'sh_addr',
                                                   'sh_offset', 'sh_size', 'sh_link', 'sh_info',
                                                   'sh_addralign', 'sh_entsize'])
# typedef struct
# {
#   Elf64_Word	st_name;		/* Symbol name (string tbl index) */
#   unsigned char	st_info;		/* Symbol type and binding */
#   unsigned char st_other;		/* Symbol visibility */
#   Elf64_Section	st_shndx;		/* Section index */
#   Elf64_Addr	st_value;		/* Symbol value */
#   Elf64_Xword	st_size;		/* Symbol size */
# } Elf64_Sym;


#define ELF32_ST_BIND(val)		(((unsigned char) (val)) >> 4)
#define ELF32_ST_TYPE(val)		((val) & 0xf)
#define ELF32_ST_INFO(bind, type)	(((bind) << 4) + ((type) & 0xf))

#define ELF64_ST_BIND(val)		ELF32_ST_BIND (val)
#define ELF64_ST_TYPE(val)		ELF32_ST_TYPE (val)
#define ELF64_ST_INFO(bind, type)	ELF32_ST_INFO ((bind), (type))

ELF64_ST_BIND = lambda x: (x & 0xff) >> 4
ELF64_ST_TYPE = lambda x: x & 0xf
ELF64_ST_INFO = lambda bind, type: bind << 4 | type & 0xf

STB_LOCAL      = 0               # Local symbol */
STB_GLOBAL     = 1               # Global symbol */
STB_WEAK       = 2               # Weak symbol */
STB_NUM        = 3               # Number of defined types.  */
STB_LOOS       = 10              # Start of OS-specific */
STB_GNU_UNIQUE = 10              # Unique symbol.  */
STB_HIOS       = 12              # End of OS-specific */
STB_LOPROC     = 13              # Start of processor-specific */
STB_HIPROC     = 15              # End of processor-specific */

STT_NOTYPE     = 0               # Symbol type is unspecified */
STT_OBJECT     = 1               # Symbol is a data object */
STT_FUNC       = 2               # Symbol is a code object */
STT_SECTION    = 3               # Symbol associated with a section */
STT_FILE       = 4               # Symbol's name is file name */
STT_COMMON     = 5               # Symbol is a common data object */
STT_TLS        = 6               # Symbol is thread-local data object*/
STT_NUM        = 7               # Number of defined types.  */
STT_LOOS       = 10              # Start of OS-specific */
STT_GNU_IFUNC  = 10              # Symbol is indirect code object */
STT_HIOS       = 12              # End of OS-specific */
STT_LOPROC     = 13              # Start of processor-specific */
STT_HIPROC     = 15		# End of processor-specific */


#define STN_UNDEF	0		/* End of a chain.  */

ELF64_SYM_FMT = "IBBHQQ"
ELF64_Sym = collections.namedtuple('ELF64_Sym', ['st_name', 'st_info', 'st_other', 'st_shndx', 'st_value', 'st_size'])

class ELF64:
    def __init__(self, bin):
        bin = bytearray(bin)
        ehdr = ELF64_Hdr(*struct.unpack(ELF64_HDR_FMT, bin[0:64]))
        self.bin = bin
        self.ehsize    = ehdr.e_ehsize
        self.phoff     = ehdr.e_phoff
        self.phnum     = ehdr.e_phnum
        self.phentsize = ehdr.e_phentsize
        self.shoff     = ehdr.e_shoff
        self.shnum     = ehdr.e_shnum
        self.shentsize = ehdr.e_shentsize
        # shdr = []
        # #shdr_bin = bin[self.ehdr.e_shoff:]

        # 
        # for i in range(self.shoff, shdr_end, self.shentsize):
        #     shdr.append(ELF64_Shdr(bin[i:i+64]))
        shdr_end = self.shoff + self.shnum * self.shentsize
        shdr = list(map(lambda x: ELF64_Shdr(*x), struct.iter_unpack(ELF64_SHDR_FMT, bin[self.shoff : shdr_end])))
        shstrtabhdr = shdr[ehdr.e_shstrndx]
        #shstrdict = ELF64.split_strtab(
        shstrtab = ELF64.get_section_bin(bin, shstrtabhdr)
        self.shstrtab = shstrtab
        #print(shstrtab[341:351], len(shstrtab))
        #print("TTT:", shstrtab[341:20])
        self.shdrs = {}
        self.ishdrs = []
        for hdr in shdr:
            # print(hdr, hdr.sh_name, ELF64.get_str_from_tab(shstrtab, hdr.sh_name))
            self.shdrs[ELF64.get_str_from_tab(shstrtab, hdr.sh_name)] = hdr
            self.ishdrs.append(hdr)
        symtabhdr = self.shdrs['.symtab']
        symtabbin = ELF64.get_section_bin(bin, symtabhdr)
        syms = list(map(lambda x: ELF64_Sym(*x), struct.iter_unpack(ELF64_SYM_FMT, symtabbin)))
        strtabhdr = self.shdrs['.strtab']
        #strdict = ELF64.split_strtab(
        
        strtab = ELF64.get_section_bin(bin, strtabhdr)
        self.strtab = strtab
        #self.sym_name = {}
        self.syms = list(map(lambda sym: sym._replace(st_name = ELF64.get_str_from_tab(strtab, sym.st_name)), syms))
        self.global_syms = {}
        self.func_syms = {}
        for sym in self.syms:
            if ELF64_ST_BIND(sym.st_info) in [STB_GLOBAL, STB_WEAK]:
                self.global_syms[sym.st_name] = sym
            if ELF64_ST_TYPE(sym.st_info) == STT_FUNC:
                self.func_syms[sym.st_name] = sym
        self.func_sym_ordered = sorted(list(self.func_syms.values()), key = lambda sym: sym.st_value)
        #self.syms = syms
#        print(shstrings)
#        print(shstrdict)
    def search_sym(self, pc):
        syms = self.func_sym_ordered
        if pc < syms[0].st_value:
            return None
        minloc = 0
        maxloc = len(syms) - 1
        while minloc != maxloc:
            midloc = (minloc + maxloc) >> 1
            if syms[midloc].st_value <= pc:
                minloc = midloc + 1
            else:
                maxloc = midloc
        return syms[minloc - 1]
    @staticmethod
    def get_str_from_tab(tab, index):
        return tab[index:].split(b"\0", 1)[0].decode()
    # @staticmethod
    # def split_strtab(strtab_bin):
    #     strings = strtab_bin.decode().split('\0')
    #     print(len(strings))
    #     print(strtab_bin.decode().replace("\0", ","))
    #     stringdict = {}
    #     str_ptr = 0
    #     for i in range(len(strings)):
    #         stringdict[str_ptr] = strings[i]
    #         str_ptr += len(strings[i]) + 1
    #     print(stringdict)
    #     return stringdict
    @staticmethod
    def get_section_bin(elf_bin, sec_hdr):
        return elf_bin[sec_hdr.sh_offset : sec_hdr.sh_offset + sec_hdr.sh_size]
        #self.syms = syms
#        print(shstrings)
#        print(shstrdict)

# # see /usr/include/linux/elf.h
# ELF64_HDR_FMT = "10sHHIQQQIHHHHHH"
# ELF64_Hdr = collections.namedtuple('ELF64_Hdr', ['e_ident', 'e_type', 'e_machine', 'e_version',
#                                                  'e_entry', 'e_phoff', 'e_shoff', 'e_flags',
#                                                  'e_ehsize', 'e_phentsize', 'e_phnum', 'e_shentsize',
#                                                   'e_shnum', 'e_shstrndx'])
# ELF64_SHDR_FMT = "IIQQQQIIQQ"
# ELF64_Shdr = collections.namedtuple('ELF64_Shdr', ['sh_name', 'sh_type', 'sh_flags', 'sh_addr',
#                                                    'sh_offset', 'sh_size', 'sh_link', 'sh_info',
#                                                    'sh_addralign', 'sh_entsize'])
# ELF64_ST_BIND = lambda x: (x & 0xff) >> 4
# ELF64_ST_TYPE = lambda x: x & 0xf
# ELF64_ST_INFO = lambda bind, type: bind << 4 | type & 0xf

# STB_LOCAL      = 0               # Local symbol */
# STB_GLOBAL     = 1               # Global symbol */
# STB_WEAK       = 2               # Weak symbol */
# STB_NUM        = 3               # Number of defined types.  */
# STB_LOOS       = 10              # Start of OS-specific */
# STB_GNU_UNIQUE = 10              # Unique symbol.  */
# STB_HIOS       = 12              # End of OS-specific */
# STB_LOPROC     = 13              # Start of processor-specific */
# STB_HIPROC     = 15              # End of processor-specific */

# STT_NOTYPE     = 0               # Symbol type is unspecified */
# STT_OBJECT     = 1               # Symbol is a data object */
# STT_FUNC       = 2               # Symbol is a code object */
# STT_SECTION    = 3               # Symbol associated with a section */
# STT_FILE       = 4               # Symbol's name is file name */
# STT_COMMON     = 5               # Symbol is a common data object */
# STT_TLS        = 6               # Symbol is thread-local data object*/
# STT_NUM        = 7               # Number of defined types.  */
# STT_LOOS       = 10              # Start of OS-specific */
# STT_GNU_IFUNC  = 10              # Symbol is indirect code object */
# STT_HIOS       = 12              # End of OS-specific */
# STT_LOPROC     = 13              # Start of processor-specific */
# STT_HIPROC     = 15		# End of processor-specific */


# ELF64_SYM_FMT = "IBBHQQ"
# ELF64_Sym = collections.namedtuple('ELF64_Sym', ['st_name', 'st_info', 'st_other', 'st_shndx', 'st_value', 'st_size'])
# class ELF64:
#     def split_strtab(strtab_bin):
#         strings = strtab_bin.decode().split('\0')
#         stringdict = {}
#         str_ptr = 0
#         for i in range(len(strings)):
#             stringdict[str_ptr] = strings[i]
#             str_ptr += len(strings[i]) + 1
#         return stringdict
#     def get_section_bin(elf_bin, sec_hdr):
#         return elf_bin[sec_hdr.sh_offset : sec_hdr.sh_offset + sec_hdr.sh_size]
#     def __init__(self, bin):
#         bin = bytearray(bin)
#         ehdr = ELF64_Hdr(*struct.unpack(ELF64_HDR_FMT, bin[0:64]))
#         self.bin = bin
#         self.ehsize    = ehdr.e_ehsize
#         self.phoff     = ehdr.e_phoff
#         self.phnum     = ehdr.e_phnum
#         self.phentsize = ehdr.e_phentsize
#         self.shoff     = ehdr.e_shoff
#         self.shnum     = ehdr.e_shnum
#         self.shentsize = ehdr.e_shentsize
#         # shdr = []
#         # #shdr_bin = bin[self.ehdr.e_shoff:]

#         # 
#         # for i in range(self.shoff, shdr_end, self.shentsize):
#         #     shdr.append(ELF64_Shdr(bin[i:i+64]))
#         shdr_end = self.shoff + self.shnum * self.shentsize
#         shdr = list(map(lambda x: ELF64_Shdr(*x), struct.iter_unpack(ELF64_SHDR_FMT, bin[self.shoff : shdr_end])))
#         shstrtabhdr = shdr[ehdr.e_shstrndx]
#         shstrdict = ELF64.split_strtab(ELF64.get_section_bin(bin, shstrtabhdr))
#         self.shdrs = {}
#         for hdr in shdr:
#             self.shdrs[shstrdict[hdr.sh_name]] = hdr
#         symtabhdr = self.shdrs['.symtab']
#         symtabbin = ELF64.get_section_bin(bin, symtabhdr)
#         syms = list(map(lambda x: ELF64_Sym(*x), struct.iter_unpack(ELF64_SYM_FMT, symtabbin)))
#         strtabhdr = self.shdrs['.strtab']
#         strdict = ELF64.split_strtab(ELF64.get_section_bin(bin, strtabhdr))
#         #self.sym_name = {}
#         self.syms = list(map(lambda sym: sym._replace(st_name = strdict.get(sym.st_name, "NAME_NOT_SURE")), syms))
#         self.global_syms = {}
#         self.func_syms = {}
#         for sym in self.syms:
#             if ELF64_ST_BIND(sym.st_info) in [STB_GLOBAL, STB_WEAK] and ELF64_ST_TYPE(sym.st_info) == STT_FUNC:
#                 self.global_syms[sym.st_name] = sym
#             if ELF64_ST_TYPE(sym.st_info) == STT_FUNC:
#                 self.func_syms[sym.st_name] = sym
#         self.func_sym_ordered = sorted(list(self.func_syms.values()), key = lambda sym: sym.st_value)
#         #self.syms = syms
# #        print(shstrings)
# #        print(shstrdict)
#     def search_sym(self, pc):
#         syms = self.func_sym_ordered
#         if pc < syms[0].st_value:
#             return None
#         minloc = 0
#         maxloc = len(syms) - 1
#         while minloc != maxloc:
#             midloc = (minloc + maxloc) >> 1
#             if syms[midloc].st_value <= pc:
#                 minloc = midloc + 1
#             else:
#                 maxloc = midloc
#         return syms[minloc - 1]

def filter_trace(elf, traces, root=None):
    nempty = 0
    new_trace = []
    for i, trace in enumerate(traces):
        if len(trace) and elf.search_sym(trace[0]).st_name in ['sig_alrm_prof', '_Z13sig_alrm_profiP9siginfo_tPv']:
            trace = trace[2:]
        if not len(trace):
            nempty += 1
            continue
        if root is not None:
            found = False
            for i, node in enumerate(trace):
                if elf.search_sym(node).st_name == root:
                    found = True
                    trace = trace[:i+1]
            if found:
                new_trace.append(trace)
        else:
            new_trace.append(trace)
    return new_trace, nempty
# def search_sym(syms, pc):
#     if pc < syms[0].st_value:
#         return None
#     minloc = 0
#     maxloc = len(syms) - 1
#     while minloc != maxloc:
#         midloc = (minloc + maxloc) >> 1
#         if syms[midloc].st_value <= pc:
#             minloc = midloc + 1
#         else:
#             maxloc = midloc
#     return syms[minloc - 1]

class FuncList:
    def __init__(self, traces, elf):
        self.elf = elf
        pc_hits = {}
        nempty = 0
        ntraces = 0
        for trace in traces:
            ntraces += 1
            for pc in trace:
               sym = elf.search_sym(pc)
               pc_hits.setdefault(sym.st_name, [0, 0])
               pc_hits[sym.st_name][0] += 1
            
            sym = elf.search_sym(trace[0])
            pc_hits[sym.st_name][1] += 1
        self.pc_hits = pc_hits
        self.ntraces = ntraces
    def report(self):
        items = sorted(self.pc_hits.items(), key=lambda x: x[1][1], reverse=True)
        ret = []
        ret.append("Got %d traces" % (self.ntraces))
        ret.append("%-40s%10s%10s" % ('symbol', '%total', '%self'))
        for item in items:
            ret.append("%-40s%10.2f%10.2f" % (item[0], item[1][0] / self.ntraces * 100, item[1][1] / self.ntraces * 100))
        return "\n".join(ret)

class CallForest:
    BOTTOM_UP = 0
    TOP_DOWN = 1
    class TreeNode:
        def __init__(self, pc, name):
            self.pc = pc
            self.name = name
            self.self_cnt = 0
            self.total_cnt = 0
            self.child = {}
        def to_dot_list(self, total_cnt, threshold, cnt = 0):
            ret = []
            if self.total_cnt * 1.0 / total_cnt < threshold:
                return ret, cnt
            ret.append('node_%d [label="%s\\nself:%.2f%% total:%.2f%%\\n0x%x"];' % (cnt, self.name, self.self_cnt / total_cnt * 100, self.total_cnt/ total_cnt * 100, self.pc))
            myid = cnt
            cnt += 1
            for child in self.child.values():
                child_list, child_cnt = child.to_dot_list(total_cnt, threshold, cnt)
                if len(child_list):
                    ret.extend(child_list)
                    ret.append("node_%d -> node_%d;" % (myid, cnt))
                    cnt = child_cnt
            return ret, cnt

    def __init__(self, traces, elf, direction=TOP_DOWN):
        self.elf = elf
        self.traces = traces
        self.nsamples = len(traces)
        if direction == CallForest.BOTTOM_UP:
            self.gen_bottom_up()
        else:
            self.gen_top_down()
        self.gen_forest_paths()
        self.direction = direction
    def report(self, threshold = 0.01):
        ret = ["digraph G{"]
        if self.direction == CallForest.BOTTOM_UP:
            ret.append("edge [dir=back]")
        cnt = 0
        for tree in self.forest:
            tree_dot, cnt = tree.to_dot_list(self.nsamples, threshold, cnt)
            ret.extend(tree_dot)
        ret.append("}\n")
        return "\n".join(ret)

    def gen_forest_paths(self):
        root = CallForest.TreeNode(0, "None")
        for path in self.paths:
            cur = root
            for node in path:
                sym = self.elf.search_sym(node)
                cur = cur.child.setdefault(sym.st_value, CallForest.TreeNode(sym.st_value, sym.st_name))
                cur.total_cnt += 1
            cur.self_cnt += 1
        self.forest = list(root.child.values())
    
    def gen_bottom_up(self):
        paths = []
        for trace in self.traces:
            if len(trace) >= 3:
                paths.append(list_copy(trace))
        self.paths = paths

    def gen_top_down(self):
        paths = []
        for trace in self.traces:
            if len(trace) >= 3:
                paths.append(list_copy(trace[::-1]))
        self.paths = paths
    
class CallGraph:
    class Node:
       def __init__(self, name):
           self.self_cnt = 0
           self.total_cnt = 0
           self.name = name
    class Edge:
       def __init__(self, src, dst):
           self.cnt = 0
           self.src = src
           self.dst = dst

    def __init__(self, traces, elf):
        self.elf = elf
        self.nsamples = len(traces)
        nodes = {}
        edges = {}
        for trace in traces:
            lastnode = None
            for i, t in enumerate(trace[::-1]):
                sym = elf.search_sym(t)
                node = nodes.setdefault(sym.st_name, CallGraph.Node(sym.st_name))
                node.total_cnt += 1
                if lastnode:
                    key = (lastnode.name, node.name)
                    edge = edges.setdefault(key, CallGraph.Edge(lastnode.name, node.name))
                    edge.cnt += 1
                lastnode = node
            if lastnode:
                lastnode.self_cnt += 1
        self.nodes = nodes
        self.edges = edges

    def report(self, threshold = 0.01):
        escape_name = lambda name: name.replace(".", "_dot_").replace("@", "_at_")
        ret = ["digraph G{"]
        present = set([])
        for node in self.nodes.values():
            total_occ = node.total_cnt * 1.0 / self.nsamples
            self_occ = node.self_cnt * 1.0 / self.nsamples
            if total_occ > threshold:
                ret.append('%s[label="%s\\nself: %.2f%%, total: %.2f%%"]' % (escape_name(node.name), node.name, self_occ * 100, total_occ * 100))
                present.add(node.name)
        for edge in self.edges.values():
            if edge.src in present and edge.dst in present:
                ret.append('%s -> %s [label="%.2f%%"]' % (escape_name(edge.src), escape_name(edge.dst), edge.cnt / self.nsamples * 100))
        ret.append("}")
        return "\n".join(ret)


if __name__ == '__main__':
    import argparse
    import sys
    import os
    def render_dot(dotpath, verbose = False):
        import subprocess
        prefix = os.path.splitext(dotpath)[0]
        print('rendering %s to %s ...' % (dotpath, prefix + '.pdf'))
        subprocess.check_output(["dot", "-Tpdf", dotpath, "-o", prefix + ".pdf"])
    parser = argparse.ArgumentParser(description="Analyze and visualize backtrace binaries.")
    parser.add_argument('-e', '--elf', help="path to ELF file.", required=True)
    parser.add_argument('-b', '--bin', help="path to sampled binary file.", required=True)
    parser.add_argument('-T', '--no-top-down', help="do not generate top-down tree.", action="store_true")
    parser.add_argument('-B', '--no-bottom-up', help="do not generate bottom-up tree.", action="store_true")
    parser.add_argument('-G', '--no-call-graph', help="do not generate call graph.", action="store_true")
    parser.add_argument('-L', '--no-list', help="do not generate function list text.", action="store_true")
    parser.add_argument('-p', '--prefix', help="output file prefix.", default=None)
    parser.add_argument('-r', '--render', help="render dot files to pdf", default=False, action="store_true")
    parser.add_argument('-q', '--quiet', help="supress commandline hits", default=False, action="store_true")
    parser.add_argument('-t', '--threshold', help="threshold for call tree structures", default=0.01, type=float, action="store")
    parser.add_argument('-R', '--root', help="filter trace with root node", default=None, action="store")
    args = parser.parse_args(sys.argv[1:])
    
    if not args.prefix:
        args.prefix = os.path.basename(args.elf)
    
    traces = read_trace(args.bin)
    
    elf = ELF64(open(args.elf, "rb").read())
    nrawtraces = len(traces)
    traces, nempty = filter_trace(elf, traces, args.root)
    print("Got %d traces, %d empty %d traces under root" % (nrawtraces, nempty, len(traces)))
    render_queue = []
    if not args.no_list:
        path = "%s-func-list.txt" % args.prefix
        if not args.quiet:
            print("reporting function list to %s..." % path)

        func_list = FuncList(traces, elf).report()
        open(path, "w").write(func_list)

    if not args.no_top_down:
        path = "%s-top-down.dot" % args.prefix
        if not args.quiet:
            print("reporting top-down tree to %s..." % path)

        top_down = CallForest(traces, elf, CallForest.TOP_DOWN).report(args.threshold)
        open(path, "w").write(top_down)
        render_queue.append(path)
    if not args.no_bottom_up:
        path = "%s-bottom-up.dot" % args.prefix
        if not args.quiet:
            print("reporting bottom-up tree to %s..." % path)

        bottom_up = CallForest(traces, elf, CallForest.BOTTOM_UP).report(args.threshold)
        open(path, "w").write(bottom_up)
        render_queue.append(path)
    if not args.no_call_graph:
        path = "%s-call-graph.dot" % args.prefix
        if not args.quiet:
            print("reporting call graph to %s..." % path)

        call_graph = CallGraph(traces, elf).report(args.threshold)
        open(path, "w").write(call_graph)
        render_queue.append(path)
    if args.render:
        for item in render_queue:
            render_dot(item, verbose = not args.quiet)
